#include <stdlib.h>
#include <stdio.h>
#define IBR_MASK 0xFFFFF
#define MAR_MASK 0xFFF

typedef struct {
    unsigned int IR : 8;
    unsigned int IBR : 20;
    unsigned int PC : 12;
    unsigned int MAR : 12;
    unsigned int leftRequired : 1;
    unsigned int nextInstIBR : 1;
} PCU; //Program Control Unit

typedef struct {
    unsigned long int AC : 40;
    unsigned long int MQ : 40;
    unsigned long int MBR : 40;
    void (*operacao)();
    //barramentos internos?
} ALU; //Arithmetic-logic Unit

struct somadores {
    int sumDataTransfer;
    int sumUncondJump;
    int sumCondJump;
    int sumArithmetic;
    int sumAddressModify;    
};

PCU uc;
extern ALU ula;
extern int startIndex;
extern struct somadores s;

void inicializaUC();
void busca();
void decodifica();
void decodificaOperacao();
void printUcRegs();
void printEstatisticas();


/* Declarações de procedimentos da ULA */
void ADD();
void ADDAbs();
void SUB();
void SUBAbs();
void LOADMQM();
void LOADMQ();
void MUL();
void DIV();
void LSH();
void RSH();
void LOAD();
void LOADNeg();
void LOADAbs();
void LOADAbsNeg();
void JUMPRight();
void JUMPLeft();
void JUMPCondRight();
void JUMPCondLeft();
void STORRight();
void STORLeft();
void STOR();


void inicializaUC() {
    uc.IR = 0;
    uc.IBR = 0;
    uc.PC = startIndex;
    uc.MAR = 0;
    uc.nextInstIBR = 0;
    uc.leftRequired = 1;
}

void decodificaOperacao() {     
    switch (uc.IR) {
        case 0x00:
            printf("EXIT\n\n"); 
            printMem();
            printEstatisticas();
            printf("Simulador encerrado com sucesso!\n");
            exit(EXIT_SUCCESS);
            break;
        case 0x01:
            printf("LOAD M(%u)\n", uc.MAR);
            ula.operacao = LOAD;
            s.sumDataTransfer++;
            break;
        case 0x02:
            printf("LOAD -M(%u)\n", uc.MAR);
            ula.operacao = LOADNeg;
            s.sumDataTransfer++;
            break;
        case 0x03:
            printf("LOAD |M(%u)|\n", uc.MAR);
            ula.operacao = LOADAbs;
            s.sumDataTransfer++;
            break;
        case 0x04:
            printf("LOAD -|M(%u)|\n", uc.MAR);
            ula.operacao = LOADAbsNeg;
            s.sumDataTransfer++;
            break;
        case 0x05:
            printf("ADD M(%u)\n", uc.MAR);
            ula.operacao = ADD;
            s.sumArithmetic++;
            break;
        case 0x06:
            printf("SUB M(%u)\n", uc.MAR);
            ula.operacao = SUB;
            s.sumArithmetic++;
            break;
        case 0x07:
            printf("ADD |M(%u)|\n", uc.MAR);
            ula.operacao = ADDAbs;
            s.sumArithmetic++;
            break;
        case 0x08:
            printf("SUB |M(%u)|\n", uc.MAR);
            ula.operacao = SUBAbs;
            s.sumArithmetic++;
            break;
        case 0x09:
            printf("LOAD MQ,M(%u)\n", uc.MAR);
            ula.operacao = LOADMQM;
            s.sumDataTransfer++;
            break;
        case 0x0A:
            printf("LOAD MQ\n");
            ula.operacao = LOADMQ;
            s.sumDataTransfer++;
            break;
        case 0x0B:
            printf("MUL M(%u)\n", uc.MAR);
            ula.operacao = MUL;
            s.sumArithmetic++;
            break;
        case 0x0C:
            printf("DIV M(%u)\n", uc.MAR);
            ula.operacao = DIV;
            s.sumArithmetic++;
            break;
        case 0x0D:
            printf("JUMP M(%u,0:19)\n", uc.MAR);
            ula.operacao = JUMPLeft;
            s.sumUncondJump++;
            break;
        case 0x0E:
            printf("JUMP M(%u,20:39)\n", uc.MAR);
            ula.operacao = JUMPRight;
            s.sumUncondJump++;
            break;
        case 0x0F:
            printf("JUMP +M(%u,0:19)\n", uc.MAR);
            ula.operacao = JUMPCondLeft;
            s.sumCondJump++;
            break;
        case 0x10:
            printf("JUMP +M(%u,20:39)\n", uc.MAR);
            ula.operacao = JUMPCondRight;
            s.sumCondJump++;
            break;
        case 0x12:
            printf("STOR M(%u,8:19)\n", uc.MAR);
            ula.operacao = STORLeft;
            s.sumAddressModify++;
            break;
        case 0x13:
            printf("STOR M(%u,28:39)\n", uc.MAR);
            ula.operacao = STORRight;
            s.sumAddressModify++;
            break;
        case 0x14:
            printf("LSH\n");
            ula.operacao = LSH;
            s.sumArithmetic++;
            break;
        case 0x15:
            printf("RSH\n");
            ula.operacao = RSH;
            s.sumArithmetic++;
            break;
        case 0x21:
            printf("STOR M(%u)\n", uc.MAR);
            ula.operacao = STOR;
            s.sumDataTransfer++;
            break;
        default:
            fprintf(stderr, "Operacao %2X não conhecida!", uc.IR);
            abort();
    }    
}

void decodifica() {
    if (uc.leftRequired) {
        uc.IBR = ula.MBR & IBR_MASK;
        ula.MBR >>= 20;
        uc.MAR = ula.MBR & MAR_MASK;
        ula.MBR >>= 12;
        uc.IR = ula.MBR;
        uc.nextInstIBR = 1;
    } else {
        uc.MAR = ula.MBR & MAR_MASK;
        ula.MBR >>= 12;
        uc.IR = ula.MBR;
        uc.leftRequired = 1;
        uc.PC++;
    }
    decodificaOperacao();
}

void decodificaIBR() {
    uc.MAR = uc.IBR & MAR_MASK;
    uc.IBR >>= 12;
    uc.IR = uc.IBR;
    uc.IBR = 0;
    decodificaOperacao();
    uc.nextInstIBR = 0;
    uc.PC++;
}

void printUcRegs() {
    printf("UC:\n");
    printf("    PC: %03X - d: %lu\n", uc.PC, uc.PC);
    printf("    IR: %02X - d: %lu\n", uc.IR, uc.IR);
    printf("   MAR: %03X - d: %lu\n", uc.MAR, uc.MAR);
    printf("   IBR: %05X - d: %lu\n\n", uc.IBR, uc.IBR);    
}

void printEstatisticas() {
    int sumTotal = s.sumAddressModify + s.sumArithmetic + s.sumCondJump + s.sumDataTransfer + s.sumUncondJump;
    printf("Estatísticas de operações:\n");
    printf("Total de Instruções: %d\n", sumTotal);
    printf("Transferência de Dados: %.2f%%\n", (float) s.sumDataTransfer / sumTotal * 100);
    printf("Saltos Incondicionais: %.2f%%\n", (float) s.sumUncondJump / sumTotal * 100);
    printf("Saltos Condicionais: %.2f%%\n", (float) s.sumCondJump / sumTotal * 100);
    printf("Aritméticas: %.2f%%\n", (float) s.sumArithmetic / sumTotal * 100);
    printf("Modificação de Endereço: %.2f%%\n\n", (float) s.sumAddressModify / sumTotal * 100);
}

void busca() {
    if (!uc.nextInstIBR) {
        uc.MAR = uc.PC;               
        loadMem();        
        decodifica();
    } else {
        decodificaIBR();
    }
}